package com.wmx.poi.word;

import cn.hutool.core.util.ZipUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.xwpf.usermodel.*;
import org.junit.Test;
import org.springframework.util.Assert;

import java.io.*;
import java.net.URLDecoder;
import java.security.SecureRandom;
import java.util.*;

/**
 * 根据 Word 文件模板动态替换占位符生成最终文件——这样的好处是可以保持和源文件一样的样式。
 * --经过实测：
 * 1、.doc 源文件替换生成完全没问题
 * 2、.docx 源文件替换生成会出现样式消失的问题，即生成的文件没有了源文件的样式.
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2021/11/8 19:52
 */
@SuppressWarnings("all")
public class TemplateDocTest {

    /**
     * word 模板生成最终 word 文件————— doc 格式生成是完全可以的.
     *
     * @param srcPath  ：源 word 模板文件地址,如 D:/word/预算公开配置功能模板.doc
     * @param destPath ：填充数据后保存的目标文件地址，如：E:/桌面/a.doc
     * @param dataMap  ：需要填充的数据，其中的 key 需要与模板文件中的占位符相同，比如 key= {year}
     * @return ：返回是否生成成功
     * @throws IOException
     */
    public static void wordTemplateGenerationForDoc(String srcPath, String destPath, Map<String, String> dataMap) throws IOException {
        Assert.hasLength(srcPath, "srcPath 源模板文件地址不能为空!");
        Assert.hasLength(destPath, "destPath 目标文件存储地址不能为空!");
        Assert.notEmpty(dataMap, "dataMap 填充数据不能为空！");
        Assert.isTrue(srcPath.endsWith(".doc"), "模板文件必须是 .doc 格式！");
        Assert.isTrue(destPath.endsWith(".doc"), "目标文件必须是 .doc 格式！");

        FileInputStream fileInputStream = new FileInputStream(srcPath);
        wordTemplateGenerationForDoc(fileInputStream, destPath, dataMap);
    }

    /**
     * word 模板生成最终 word 文件————— doc 格式生成是完全可以的.
     *
     * @param inputStream ：源 word 模板文件地址,如 D:/word/预算公开配置功能模板.doc
     * @param destPath    ：填充数据后保存的目标文件地址，如：E:/桌面/a.doc
     * @param dataMap     ：需要填充的数据，其中的 key 需要与模板文件中的占位符相同，比如 key= {year}
     * @return ：返回是否生成成功
     * @throws IOException
     */
    public static void wordTemplateGenerationForDoc(InputStream inputStream, String destPath, Map<String, String> dataMap) throws IOException {
        Assert.hasLength(destPath, "destPath 目标文件存储地址不能为空!");
        Assert.notEmpty(dataMap, "dataMap 填充数据不能为空！");
        Assert.isTrue(destPath.endsWith(".doc"), "目标文件必须是 .doc 格式！");
        Assert.notNull(inputStream, "源 word 模板文件不能为空！");

        //HWPFDocument(InputStream inputStream)：从 InputStream 输入流中加载 Word文 档
        HWPFDocument document = new HWPFDocument(inputStream);
        //Range getRange()：返回覆盖整个文档的范围，但不包括任何页眉和页脚。
        Range range = document.getRange();
        for (Map.Entry<String, String> entry : dataMap.entrySet()) {
            range.replaceText(entry.getKey(), entry.getValue());
        }
        //输出文件
        FileOutputStream outStream = new FileOutputStream(destPath);
        document.write(outStream);
        outStream.flush();
        outStream.close();
        System.out.println("生成目标文件：" + destPath);
    }

    /**
     * word 模板生成最终 word 文件————— doc 格式生成是完全可以的.
     *
     * @param inputStream ：源 word 模板文件地址,如 D:/word/预算公开配置功能模板.doc
     * @param dataMap     ：需要填充的数据，其中的 key 需要与模板文件中的占位符相同，比如 key= {year}
     * @return 返回生成好的目标文件的字节数组输出流，这样方便用于 web 环境时，转换好目标文件后，服务器不用存储文件，也能直接进行浏览器下载。
     * @throws IOException
     */
    public static ByteArrayOutputStream wordTemplateGenerationForDoc(InputStream inputStream, Map<String, String> dataMap) throws IOException {
        Assert.notEmpty(dataMap, "dataMap 填充数据不能为空！");
        Assert.notNull(inputStream, "源 word 模板文件不能为空！");

        //HWPFDocument(InputStream inputStream)：从 InputStream 输入流中加载 Word文 档
        HWPFDocument document = new HWPFDocument(inputStream);
        //Range getRange()：返回覆盖整个文档的范围，但不包括任何页眉和页脚。
        Range range = document.getRange();
        for (Map.Entry<String, String> entry : dataMap.entrySet()) {
            range.replaceText(entry.getKey(), entry.getValue());
        }
        //输出文件
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        document.write(outStream);
        outStream.flush();
        outStream.close();
        System.out.println("wordTemplateGenerationForDoc2 生成目标字节数组输出流：" + outStream.size());
        return outStream;
    }

    /**
     * byteArrayOutputStream 输出流 转 ByteArrayInputStream 输入流
     *
     * @param byteArrayOutputStream
     * @return
     * @throws Exception
     */
    public ByteArrayInputStream outputStream2InputStream(final ByteArrayOutputStream byteArrayOutputStream) {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        return byteArrayInputStream;
    }

    /**
     * 测试 .doc 文件模板转换，返回 ByteArrayOutputStream 流，用于 web 环境浏览器下载
     *
     * @throws IOException
     */
    @Test
    public void testWordTemplateGenerationForDoc() throws IOException {
        String destPath = "D:\\" + System.currentTimeMillis() + ".zip";
        OutputStream destOutputStream = new FileOutputStream(new File(destPath));
        Map<String, String> dataMap = getDataMaps();

        //被压缩文件的输入流
        List<InputStream> inputStreams = new ArrayList<>();
        //压缩包中文件对应的名字
        String[] paths = {"得瑟得瑟.doc", "和冬季防火等级.doc", "的肌肤的.doc", "酒店房间的.doc", "的教会大家.doc"};

        for (int i = 0; i < 5; i++) {
            InputStream inputStream = TemplateDocTest.class.getClassLoader().getResourceAsStream("word/预算公开配置功能模板.doc");
            ByteArrayOutputStream outputStream = wordTemplateGenerationForDoc(inputStream, dataMap);
            ByteArrayInputStream byteArrayInputStream = outputStream2InputStream(outputStream);
            inputStreams.add(byteArrayInputStream);
        }
        /**
         * zip(OutputStream out, String[] paths, InputStream[] ins)：将文件流压缩到目标流中
         *
         * @param out   目标流，压缩完成自动关闭，web 环境时，使用 response.getOutputStream() 返回 ServletOutputStream 输出即可进行网络下载.
         * @param paths 流数据在压缩文件中的路径或文件名，可以自己随意定义，会按顺序给后面的 ins 进行命名.
         * @param ins   要压缩的源，添加完成后自动关闭流
         */
        ZipUtil.zip(destOutputStream, paths, inputStreams.toArray(new InputStream[inputStreams.size()]));
        System.out.println("生成压缩文件：" + destPath);
    }

    /**
     * 测试 .doc 格式的 word 模板生成最终 word 文件————— doc 格式生成是完全可以的.
     *
     * @throws IOException
     */
    @Test
    public void testWordTemplateGenerationByDoc() throws Exception {
        String classPath = "word/预算公开配置功能模板.doc";
        String srcPath = TemplateDocTest.class.getClassLoader().getResource(classPath).getPath();
        //转码前：/D:/project/IDEA_project/apache-poi-study/target/classes/word/%e9%a2%84%e7%ae%97%e5%85%ac%e5%bc%80%e9%85%8d%e7%bd%ae%e5%8a%9f%e8%83%bd%e6%a8%a1%e6%9d%bf.doc
        System.out.println("转码前：" + srcPath);
        srcPath = URLDecoder.decode(srcPath, "utf-8");
        ///转码后：/D:/project/IDEA_project/apache-poi-study/target/classes/word/预算公开配置功能模板.doc
        System.out.println("转码后：" + srcPath);

        String destPath = "E:\\temp\\" + System.currentTimeMillis() + ".doc";
        Map<String, String> dataMap = getDataMaps();
        wordTemplateGenerationForDoc(srcPath, destPath, dataMap);
    }

    /**
     * 测试 .doc 格式的 word 模板生成最终 word 文件————— doc 格式生成是完全可以的.
     *
     * @throws IOException
     */
    @Test
    public void testWordTemplateGenerationDoc() throws IOException {
        InputStream inputStream = TemplateDocTest.class.getClassLoader().getResourceAsStream("word/预算公开配置功能模板.doc");
        String destPath = "E:\\temp\\" + System.currentTimeMillis() + ".doc";
        Map<String, String> dataMap = getDataMaps();

        wordTemplateGenerationForDoc(inputStream, destPath, dataMap);
    }

    /**
     * 获取需要插入到文档中的数据
     *
     * @return
     */
    private Map<String, String> getDataMaps() {
        Map<String, String> dataMap = new LinkedHashMap<>(64);

        dataMap.put("{fiscal_year}", "2021");
        dataMap.put("{year}", "2021");
        dataMap.put("{name}", "蚩尤后裔");
        dataMap.put("{agency_name}", " 邵阳市北塔区财政局");
        int start = 1;
        int end = 11;
        for (int i = start; i <= end; i++) {
            dataMap.put("{num" + i + "}", String.valueOf(new SecureRandom().nextInt(100) + 3));
        }
        start = 12;
        end = 55;
        for (int i = start; i <= end; i++) {
            dataMap.put("{num" + i + "}", String.valueOf(new SecureRandom().nextFloat() * 10000));
        }
        start = 1;
        end = 10;
        for (int i = start; i <= end; i++) {
            dataMap.put("{no" + i + "}", String.valueOf(i));
        }
        int row = 3;
        int column = 5;
        for (int i = 1; i <= row; i++) {
            for (int j = 1; j <= column; j++) {
                dataMap.put("{col" + i + j + "}", String.valueOf(new SecureRandom().nextFloat() * 10000));
            }
        }
        dataMap.put("{str1}", "防洪救灾");
        dataMap.put("{str2}", "防洪办");
        dataMap.put("{str3}", "预算");
        dataMap.put("{oldYear}", "2021");
        return dataMap;
    }


}
